// 在用户和内核空间之间切换的代码，由 ld.script 使其与页面边界对齐。

// 数据保存寄存器 0，用于软件临时保存寄存器的值
#define LOONGARCH_CSR_SAVE0 0x30 
// 低半地址空间全局目录基址
#define LOONGARCH_CSR_PGDL 0x19    

.section u_eentry
.globl uservec
.align 4

uservec:    
        // trap.c 把 eentry 指向这里，所以用户空间中断在内核态特权级从这里开始，还带有一个用户页表
        // CSR.SAVE 指向进程的 p->trapframe 成员，映射到用户空间的 TRAPFRAME
        
	// 交换 a0 和 SAVE0 寄存器，这样 a0 就是 TRAPFRAME
        csrwr  $a0, LOONGARCH_CSR_SAVE0

        // 用户寄存器保存到 TRAPFRAME
        st.d   $ra, $a0, 0
        st.d   $tp, $a0, 8
        st.d   $sp, $a0, 16
        st.d   $a1, $a0, 32
        st.d   $a2, $a0, 40
        st.d   $a3, $a0, 48
        st.d   $a4, $a0, 56
        st.d   $a5, $a0, 64
        st.d   $a6, $a0, 72
        st.d   $a7, $a0, 80
        st.d   $t0, $a0, 88
        st.d   $t1, $a0, 96
        st.d   $t2, $a0, 104
        st.d   $t3, $a0, 112
        st.d   $t4, $a0, 120
        st.d   $t5, $a0, 128
        st.d   $t6, $a0, 136
        st.d   $t7, $a0, 144
        st.d   $t8, $a0, 152
        st.d   $r21, $a0,160
        st.d   $fp, $a0, 168
        st.d   $s0, $a0, 176
        st.d   $s1, $a0, 184
        st.d   $s2, $a0, 192
        st.d   $s3, $a0, 200
        st.d   $s4, $a0, 208
        st.d   $s5, $a0, 216
        st.d   $s6, $a0, 224
        st.d   $s7, $a0, 232
        st.d   $s8, $a0, 240

	// a0 寄存器值换成 p->trapframe->a0
        csrrd  $t0, LOONGARCH_CSR_SAVE0
        st.d   $t0, $a0, 24

        // 从 p->trapframe->kernel_sp 恢复内核栈指针给 sp 寄存器
        ld.d   $sp, $a0, 248

        // 从 p->trapframe->kernel_hartid 读取 hartid 给 tp 寄存器
        ld.d   $tp, $a0, 272

        // 从 p->trapframe->kernel_trap 读取 usertrap() 地址给 t0
        ld.d   $t0, $a0, 256

        // 从 p->trapframe->kernel_pgdl 恢复内核页表
        ld.d   $t1, $a0, 280
        csrwr  $t1, LOONGARCH_CSR_PGDL
        invtlb 0x0, $zero, $zero

        // 跳转到 usertrap()
        jirl   $zero, $t0, 0

.globl userret
userret:
        // userret(TRAPFRAME, pagetable)
        // 从内核态切换到用户态
        // usertrapret() 调用此函数
        // a0: TRAPFRAME, 在用户页页表
        // a1: 用户页页表, 用于 CSR.PGDL

        // 切换到用户页页表
        csrwr  $a1, LOONGARCH_CSR_PGDL
        invtlb 0x0, $zero, $zero

        // 保存 a0 到 SAVE0, 这样我们就可以在最后一步把它和 a0(TRAPFRAME) 交换
        ld.d   $t0, $a0, 24
        csrwr  $t0, LOONGARCH_CSR_SAVE0

        // 从 TRAPFRAME 恢复 a0 之外的寄存器
        ld.d   $ra, $a0, 0
        ld.d   $tp, $a0, 8
        ld.d   $sp, $a0, 16
        ld.d   $a1, $a0, 32
        ld.d   $a2, $a0, 40
        ld.d   $a3, $a0, 48
        ld.d   $a4, $a0, 56
        ld.d   $a5, $a0, 64
        ld.d   $a6, $a0, 72
        ld.d   $a7, $a0, 80
        ld.d   $t0, $a0, 88
        ld.d   $t1, $a0, 96
        ld.d   $t2, $a0, 104
        ld.d   $t3, $a0, 112
        ld.d   $t4, $a0, 120
        ld.d   $t5, $a0, 128
        ld.d   $t6, $a0, 136
        ld.d   $t7, $a0, 144
        ld.d   $t8, $a0, 152
        ld.d   $r21, $a0,160
        ld.d   $fp, $a0, 168
        ld.d   $s0, $a0, 176
        ld.d   $s1, $a0, 184
        ld.d   $s2, $a0, 192
        ld.d   $s3, $a0, 200
        ld.d   $s4, $a0, 208
        ld.d   $s5, $a0, 216
        ld.d   $s6, $a0, 224
        ld.d   $s7, $a0, 232
        ld.d   $s8, $a0, 240

	// 用 SAVE 的 TRAPFRAME 恢复 a0 
        csrwr  $a0, LOONGARCH_CSR_SAVE0
        
        // 返回用户态，让 usertrapret() 负责设定 prmd 和 era。
        ertn
